#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_openal.h"

/*//. Generic Object Stuff
  ../ Pretty sure this was copy paste from another OOP module. As if I remember
  ../ which one that was. .//*/

//. create new object.
zend_object_value
obj_openal_source_new(zend_class_entry *type TSRMLS_DC) {
	obj_openal_source_s *tmp;
	return obj_openal_source_mknew(type, &tmp TSRMLS_CC);
}

zend_object_value
obj_openal_source_mknew(zend_class_entry *type, obj_openal_source_s **obj TSRMLS_DC) {
	zend_object_value output;
	obj_openal_source_s *iobj;
	
	iobj = emalloc(sizeof(obj_openal_source_s));
	memset(iobj, 0, sizeof(obj_openal_source_s));
	
	iobj->std.ce = type;
	*obj = iobj;
	
	ALLOC_HASHTABLE(iobj->std.properties);
	zend_hash_init(iobj->std.properties,
		0,
		NULL,
		ZVAL_PTR_DTOR,
		0
	);
	zend_hash_copy(
		iobj->std.properties,
		&type->default_properties,
		(copy_ctor_func_t)zval_add_ref,
		NULL,
		sizeof(zval *)
	);
	
	output.handle = zend_objects_store_put(
		iobj,
		NULL,
		(zend_objects_free_object_storage_t)obj_openal_source_free,
		obj_openal_source_clone TSRMLS_CC
	);
	output.handlers = &obj_openal_source_handlers;

	return output;
}

void
obj_openal_source_clone(void *object, void **object_clone TSRMLS_DC)
{
	zend_throw_exception(
		zend_exception_get_default(TSRMLS_C),
		"Unable to clone OpenALBuffer Objects",
		0 TSRMLS_CC
	);
}


void
obj_openal_source_free(void *obj TSRMLS_DC) {
	obj_openal_source_s *iobj = (obj_openal_source_s *)obj;

	zend_hash_destroy(iobj->std.properties);
	FREE_HASHTABLE(iobj->std.properties);
	
	if(alIsSource(iobj->src)) {
		alSourceStop(iobj->src);
		alDeleteSources((ALint)1,&iobj->src);
	}
	
	efree(obj);
}

/*//. Magical Properties
  ../ Supports OpenAL Buffer Read Only Properties, and defaulting back to
  ../ standard operations for non-reserved ones. .//*/

void
obj_openal_source_choose_property(const char *property, obj_openal_source_prop_s * al) {

	al->prop = AL_NONE;
	al->type = 0;
	al->size = 1;

	if(!strcmp(property,"buffer")) { al->prop = AL_BUFFER;  }
	else if(!strcmp(property,"buffersProcessed")) { al->prop = AL_BUFFERS_QUEUED; al->type = TYPE_ALINT; al->size = -1; }
	else if(!strcmp(property,"buffersQueued")) { al->prop = AL_BUFFERS_PROCESSED; al->type = TYPE_ALINT; al->size = -1; }
	else if(!strcmp(property,"byteOffset")) { al->prop = AL_BYTE_OFFSET; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"direction")) { al->prop = AL_DIRECTION; al->type = TYPE_ALFLOAT; al->size = 3; }
	else if(!strcmp(property,"gain")) { al->prop = AL_GAIN; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"gainMax")) { al->prop = AL_MIN_GAIN; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"gainMin")) { al->prop = AL_MAX_GAIN; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"innerAngle")) { al->prop = AL_CONE_INNER_ANGLE; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"loop")) { al->prop = AL_LOOPING; al->type = TYPE_ALBOOLEAN; }
	else if(!strcmp(property,"maxDistance")) { al->prop = AL_MAX_DISTANCE; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"msecOffset")) { al->prop = AL_SEC_OFFSET; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"outerAngle")) { al->prop = AL_CONE_OUTER_ANGLE; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"outerGain")) { al->prop = AL_CONE_OUTER_GAIN; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"pitch")) { al->prop = AL_PITCH; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"position")) { al->prop = AL_POSITION; al->type = TYPE_ALFLOAT; al->size = 3; }
	else if(!strcmp(property,"referenceDistance")) { al->prop = AL_REFERENCE_DISTANCE; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"rolloffFactor")) { al->prop = AL_ROLLOFF_FACTOR; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"sampleOffset")) { al->prop = AL_SAMPLE_OFFSET; al->type = TYPE_ALFLOAT; }
	else if(!strcmp(property,"sourceRelative")) { al->prop = AL_SOURCE_RELATIVE; al->type = TYPE_ALBOOLEAN; }
	else if(!strcmp(property,"state")) { al->prop = AL_SOURCE_STATE; al->type = TYPE_ALINT; al->size = -1; }
	else if(!strcmp(property,"velocity")) { al->prop = AL_VELOCITY; al->type = TYPE_ALFLOAT; al->size = 3; }
	return;
}


zval * 
obj_openal_source_read_property(zval *object, zval *member, int type TSRMLS_DC)
{
	char *property;
	zval *return_value = NULL, *zreal = NULL;
	obj_openal_source_s *this = NULL;
   
	this = (obj_openal_source_s *)zend_objects_get_address(object TSRMLS_CC);
	property = Z_STRVAL_P(member);

	obj_openal_source_prop_s al;
	obj_openal_source_choose_property(property, &al);
	if(al.size < 0) { al.size = (al.size * -1); }

	/*//. Special Types 
	  ../ requires objects and or stuff. .//*/

	if(al.prop == AL_BUFFER) {
		if(this->zbuffer) {
			return_value = zend_get_std_object_handlers()->read_property(
				object,
				member,
				type TSRMLS_CC
			);
		} else {
			MAKE_STD_ZVAL(return_value);
			ZVAL_NULL(return_value);
		}
	}

	/*//. Generic Types 
	  ../ Fill in the blanks. .//*/
	
	else if(al.size == 1 && al.type == TYPE_ALBOOLEAN) {
		ALint bvalue;
		alGetSourcei(this->src, al.prop, &bvalue);

		MAKE_STD_ZVAL(return_value);
		if(bvalue == AL_TRUE) {
			ZVAL_BOOL(return_value,1);
		} else {
			ZVAL_BOOL(return_value,0);
		}
	}

	else if(al.size == 1 && al.type == TYPE_ALINT) {
		ALint ivalue;
		alGetSourcei(this->src, al.prop, &ivalue);
		MAKE_STD_ZVAL(return_value);
		ZVAL_LONG(return_value,(long)ivalue);
	}

	else if(al.size == 1 && al.type == TYPE_ALFLOAT) {
		ALfloat fvalue;
		alGetSourcef(this->src, al.prop, &fvalue);
		MAKE_STD_ZVAL(return_value);
		ZVAL_DOUBLE(return_value,(double)fvalue);
	}
	
	else if(al.size > 1 && al.type == TYPE_ALFLOAT) {
		int a;
		ALfloat fvalue[al.size];
		MAKE_STD_ZVAL(return_value);
		array_init(return_value);
		alGetSourcefv(this->src,al.prop,(ALfloat *)&fvalue);
		
		for(a = 0; a < al.size; a++) {
			add_next_index_double(return_value, fvalue[a]);
		}
	}

	else if(al.prop == AL_NONE) {
		return_value = zend_get_std_object_handlers()->read_property(
			object,
			member,
			type TSRMLS_CC
		);
	}
	
	if(al.prop != AL_NONE || (al.prop == AL_BUFFER && this->zbuffer)) {
		return_value->refcount = 0;
		return_value->is_ref = 0;
	}

	return return_value;
}

void
obj_openal_source_write_property(zval *object, zval *member, zval *value TSRMLS_DC)
{
	int err = 0;
	char *property;
	obj_openal_source_s *this;
        
	this = (obj_openal_source_s *)zend_objects_get_address(object TSRMLS_CC);
	property = Z_STRVAL_P(member);

	obj_openal_source_prop_s al;
	obj_openal_source_choose_property(property, &al);

	/*//. Special Types 
	  ../ requires objects and or stuff. .//*/
	
	if(al.prop == AL_BUFFER) {
		char suberr = 0;
		
		if(Z_TYPE_P(value) == IS_OBJECT) {
			char *class;
			unsigned int classn;		
			
			zend_get_object_classname(
				value,
				&class,
				&classn TSRMLS_CC
			);
			
			if(!strcmp(class,"OpenALBuffer")) {
				obj_openal_buffer_s *that = (obj_openal_buffer_s *)zend_objects_get_address(value TSRMLS_CC);
				alSourcei(
					this->src,
					al.prop,
					that->buf
				);

				this->zbuffer = value;
				zend_get_std_object_handlers()->write_property(
					object,
					member,
					value TSRMLS_CC
				);
			} else {
				suberr = 1;			
			}
			
			efree(class);
		}
		
		else if(Z_TYPE_P(value) == IS_NULL) {
			if(this->zbuffer) {
				zend_get_std_object_handlers()->write_property(
					object,
					member,
					value TSRMLS_CC
				);			

				this->zbuffer = NULL;
			}
		}		
				
		else {
			suberr = 1;
		}
		
		if(suberr) {
			php_error(
				E_WARNING,
				"Property '%s' expects object of type OpenALBuffer",
				property
			);		
		}	
	}	

	/*//. Generic Types 
	  ../ Fill in the blanks. .//*/

	else if(al.size == 1 && al.type == TYPE_ALBOOLEAN) {
		if(Z_TYPE_P(value) == IS_BOOL) {
			alSourcei(
				this->src,
				al.prop,
				(ALint)Z_BVAL_P(value)
			);
		} else {
			php_error(
				E_ERROR,
				"Property '%s' expects boolean value",
				property
			);
		}
	}

	else if(al.size == 1 && al.type == TYPE_ALINT) {
		if(Z_TYPE_P(value) == IS_LONG) {
			alSourcei(
				this->src,
				al.prop,
				(ALint)Z_LVAL_P(value)
			);
		} else {
			php_error(
				E_ERROR,
				"Property '%s' expects integer value",
				property
			);
		}
	}

	else if(al.size == 1 && al.type == TYPE_ALFLOAT) {
		if(Z_TYPE_P(value) == IS_DOUBLE) {
			alSourcef(
				this->src,
				al.prop,
				(ALfloat)Z_DVAL_P(value)
			);
		} else {
			php_error(
				E_ERROR,
				"Property '%s' expects floating-point value",
				property
			);
		}
	}

	else if(al.size > 1 && al.type == TYPE_ALFLOAT) {
		char suberr = 0;
			
		if(Z_TYPE_P(value) != IS_ARRAY) {
			suberr = 1;
		} else {	
			zval **cur = NULL;
			HashTable *data = Z_ARRVAL_P(value);

			if(zend_hash_num_elements(data) != 3) {
				suberr = 1;
			} else {
				char a;
				ALfloat fvalue[al.size];
				
				zend_hash_internal_pointer_reset(data);
				for(a = 0; a < al.size; a++) {
					zend_hash_get_current_data(data,(void **)&cur);
					zend_hash_move_forward(data);
					if(Z_TYPE_PP(cur) == IS_DOUBLE) {
						fvalue[a] = (ALfloat)Z_DVAL_PP(cur);
					} else {
						suberr = 1;
						break;					
					}
				}
				
				if(!suberr) {
					alSourcefv(
						this->src,
						al.prop,
						fvalue
					);
				}
			}
		}
		
		if(suberr) {
			php_error(
				E_WARNING,
				"Property '%s' expects %d element array of floating-point values",
				property, al.size
			);
		}
	}
	
	else if(al.size < 0) {
		php_error(
			E_WARNING,
			"Unable to write to read-only property '%s'",
			property
		);		
	}	
	
	else if(al.prop == AL_NONE) {
		zend_get_std_object_handlers()->write_property(
			object,
			member,
			value TSRMLS_CC
		);
	}
	
	return;
}

/*//. Actual OpenAL Class Methods
  ../ There be sounds here. .//*/

PHP_METHOD(OpenALSource, __construct) {
	obj_openal_source_s *this = (obj_openal_source_s *)zend_object_store_get_object(getThis() TSRMLS_CC);

	int a;
	ALuint src;
	ALfloat defval[3] = {0,0,0};

	alGenSources(1, &src);
	if(openal_error_exists()) {
		RETURN_FALSE;
	}
	
	this->zbuffer = NULL;
	this->src = src;

	alSourcef(this->src,AL_GAIN,(ALfloat)1.0f);
	alSourcef(this->src,AL_PITCH,(ALfloat)1.0f);
	alSourcei(this->src,AL_LOOPING,AL_FALSE);
	alSourcefv(this->src,AL_POSITION,defval);
	alSourcefv(this->src,AL_VELOCITY,defval);
	
	return;
}

//. Source Actions.

PHP_METHOD(OpenALSource, play) {
	obj_openal_source_s *this = (obj_openal_source_s *)zend_object_store_get_object(getThis() TSRMLS_CC);
	if(alIsSource(this->src)) {
		alSourcePlay(this->src);
	}
}

PHP_METHOD(OpenALSource, stop) {
	obj_openal_source_s *this = (obj_openal_source_s *)zend_object_store_get_object(getThis() TSRMLS_CC);
	if(alIsSource(this->src)) {
		alSourceStop(this->src);
	}
}

PHP_METHOD(OpenALSource, pause) {
	obj_openal_source_s *this = (obj_openal_source_s *)zend_object_store_get_object(getThis() TSRMLS_CC);
	if(alIsSource(this->src)) {
		alSourcePause(this->src);
	}
}

PHP_METHOD(OpenALSource, rewind) {
	obj_openal_source_s *this = (obj_openal_source_s *)zend_object_store_get_object(getThis() TSRMLS_CC);
	if(alIsSource(this->src)) {
		alSourceRewind(this->src);
	}
}

PHP_METHOD(OpenALSource, free) {
	obj_openal_source_s *this = (obj_openal_source_s *)zend_object_store_get_object(getThis() TSRMLS_CC);

	if(this->zbuffer) {
		
		zval *member, value;
		MAKE_STD_ZVAL(member);
			member->type = IS_STRING;
			member->value.str.val = estrdup("buffer");
			member->value.str.len = (sizeof("buffer") - 1);
		
		value.type = IS_NULL;
	
		zend_get_std_object_handlers()->write_property(
			getThis(),
			member,
			&value TSRMLS_CC
		);

		this->zbuffer = NULL;
		
		efree(member->value.str.val);
		efree(member);

	}

	if(alIsSource((ALuint)this->src)) {
		alSourceStop(this->src);
		alDeleteSources(1, &this->src);
	}
}
